// SSLCon.cpp: implementation of the CSslConnection class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "SSLCon.h"
//#include "cupa_rest.h"
#include "json.h"
#include "encode.h"
//#include "IC_Trans_Upload.h"

#define REST_URI "weiboCrawlerService/sinaLogin?"

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSslConnection::CSslConnection()
{
	m_dwTimeout = 30000; // in milliseconds
	m_p_stream = NULL;
	m_hSendThread = NULL;
	m_hReceiveThread = NULL;
	m_hReceiveStreamThread = NULL;
	m_lastErrorCode = 0;
	m_hInternet = NULL;
	m_hRequest = NULL;
	m_certStoreType = certStoreMY;
	m_hStore = NULL;
	m_hSession = NULL;
	m_pContext = NULL;
	m_wPort = 443;
	m_strObjectName = REST_URI;
	m_strAgentName = "Crawler";	
	m_strPostData = "";
	m_bigPost = FALSE;
	m_strVerb = "POST";
	m_ReqID = 0;
	//m_secureFlags = INTERNET_FLAG_RELOAD|INTERNET_FLAG_KEEP_CONNECTION|INTERNET_FLAG_NO_CACHE_WRITE|INTERNET_FLAG_SECURE;	//https
	m_secureFlags = INTERNET_FLAG_RELOAD|INTERNET_FLAG_KEEP_CONNECTION|INTERNET_FLAG_NO_CACHE_WRITE;	//http
}

CSslConnection::~CSslConnection()
{
	ClearHandles();
}


bool CSslConnection::ConnectToHttpsServer()
{
	try {	
		m_hInternet = InternetOpen(str_to_wstr(m_strAgentName).c_str(), INTERNET_OPEN_TYPE_PRECONFIG , 
				NULL, NULL, 0);
		if (!m_hInternet) {
			m_strLastError = "Cannot open internet";
			m_lastErrorCode = GetLastError();
			return false;
		}
			
		m_hSession = InternetConnect(m_hInternet, 
			str_to_wstr(m_strServerName).c_str(), 
			m_wPort,
			str_to_wstr(m_strUserName).c_str(), 
			str_to_wstr(m_strPassword).c_str(),
			INTERNET_SERVICE_HTTP,
			0,
			0);
		if (!m_hSession) {			
			m_strLastError = "Cannot connect to internet";
			m_lastErrorCode = GetLastError();
			ClearHandles();
			return false;
		}
		m_hRequest = HttpOpenRequest(m_hSession, 
				str_to_wstr(m_strVerb).c_str(),
				str_to_wstr(m_strObjectName).c_str(),
				NULL,
				L"",
				NULL,
				m_secureFlags, 
				m_ReqID);
		if (!m_hRequest) {
			m_strLastError = "Cannot perform http request";
			m_lastErrorCode = GetLastError();
			ClearHandles();		
			return false;
		}
		
		m_ReqID++;
	}
	catch(...) {
		m_strLastError = "Memory Exception occured";
		m_lastErrorCode = GetLastError();
		return false;
	}
	return true;
}

DWORD WINAPI ThreadProcSendHttpsRequest(LPVOID lpParam)
{
	CSslConnection *pConn = (CSslConnection *)lpParam;
	if (pConn == NULL) {
		return -1;
	}
	try {	
		LPCSTR lpPostData = NULL;
		string postData;

		if(pConn->GetPostData().length() && (pConn->GetVerb() == "POST" || pConn->GetVerb() == "PUT"))   
		{
			if (pConn->GetBigPost()) {
				HttpAddRequestHeaders(pConn->GetHRequest(), L"Content-Type: multipart/form-data; boundary=----------ae0GIdseI3cH2gL6E4f1GI3ae0\r\n", -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
				postData = "------------ae0GIdseI3cH2gL6E4f1GI3ae0\r\n";
				postData += "Content-Disposition: form-data; name=\"excuteicdata\" \r\n\r\n";
				postData += pConn->GetPostData();
				postData += "\r\n------------ae0GIdseI3cH2gL6E4f1GI3ae0--\r\n\r\n";  
				lpPostData = (LPCSTR)postData.c_str();
			} else {
				HttpAddRequestHeaders(pConn->GetHRequest(), L"Content-Type:application/x-www-form-urlencoded\r\n", -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
				postData = pConn->GetPostData();
				lpPostData = (LPCSTR)postData.c_str(); 
			}
		}

		for (int tries = 0; tries < 20; ++tries) {		
			int result =  HttpSendRequest(pConn->GetHRequest(), NULL, 0, (LPVOID)lpPostData, postData.length());
			if (result) 				
				return 0;
			int lastErr = GetLastError();
			if (lastErr == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) {
				if (!pConn->SetClientCert()) {
					pConn->SetLastErrorString("Cannot perform http request, client authentication needed but couldnt detect required client certificate");
					pConn->SetLastErrorCode(GetLastError());
					return -1;
				}					
			}
			else if (lastErr == ERROR_INTERNET_INVALID_CA) {
				pConn->SetLastErrorString("Cannot perform http request, client authentication needed, invalid client certificate is used");
				pConn->SetLastErrorCode(GetLastError());
				return -1;
			}
			else {
				pConn->SetLastErrorString("Cannot perform http request");
				pConn->SetLastErrorCode(GetLastError());
				return -1;
			}
		} 
	}
	catch(...) {
		pConn->SetLastErrorString("Memory Exception occured");
		pConn->SetLastErrorCode(GetLastError());
		return -1;
	}
	return -1;
}

bool CSslConnection::SendHttpsRequest()
{
	m_hSendThread = CreateThread(NULL, 0, ThreadProcSendHttpsRequest, (void*)this, 0, &m_dwSendThreadID);
	if (WaitForSingleObject(m_hSendThread, m_dwTimeout) == WAIT_TIMEOUT) {
		m_strLastError = "perform http request timeout";
		m_lastErrorCode = GetLastError();
        return false; 
	}
	DWORD dwExitCode = 0;
	if (!GetExitCodeThread(m_hSendThread, &dwExitCode)) {
		m_strLastError = "Error on GetExitCodeThread";
		m_lastErrorCode = GetLastError();
        return false;
    }
	if (dwExitCode == STILL_ACTIVE) {
		return 0 == m_lastErrorCode;
	}
	if (m_lastErrorCode != 0) {
		return false;
	}
	return dwExitCode < 0 ? false : true;


	/*try {	
		LPCSTR lpPostData = NULL;

		if(m_strPostData.length() && (m_strVerb == "POST" || m_strVerb == "PUT"))   
		{
			HttpAddRequestHeaders(m_hRequest, L"Content-Type:application/x-www-form-urlencoded\r\n", -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
			//HttpAddRequestHeaders(m_hRequest, L"Content-Type: multipart/form-data; boundary=----------ae0GIdseI3cH2gL6E4f1GI3ae0\r\n", -1, HTTP_ADDREQ_FLAG_ADD|HTTP_ADDREQ_FLAG_REPLACE);
			lpPostData = (LPCSTR)m_strPostData.c_str();   
		}

		for (int tries = 0; tries < 20; ++tries) {		
			int result =  HttpSendRequest(m_hRequest, NULL, 0, (LPVOID)lpPostData, m_strPostData.length());
			if (result) 				
				return true;
			int lastErr = GetLastError();
			if (lastErr == ERROR_INTERNET_CLIENT_AUTH_CERT_NEEDED) {
				if (!SetClientCert()) {
					m_strLastError = "Cannot perform http request, client authentication needed but couldnt detect required client certificate";
					m_lastErrorCode = GetLastError();
					return false;
				}					
			}
			else if (lastErr == ERROR_INTERNET_INVALID_CA) {
				m_strLastError = "Cannot perform http request, client authentication needed, invalid client certificate is used";
				m_lastErrorCode = GetLastError();
				return false;
			}
			else {
				m_strLastError = "Cannot perform http request";
				m_lastErrorCode = GetLastError();
				return false;
			}
		} 
	}
	catch(...) {
		m_strLastError = "Memory Exception occured";
		m_lastErrorCode = GetLastError();
		return false;
	}
	return false;*/
}

void CSslConnection::ClearHandles()
{
	if (m_hInternet) {
		InternetCloseHandle(m_hInternet);
		m_hInternet = NULL;
	}
		
	if (m_hSession) {
		InternetCloseHandle(m_hSession);
		m_hSession = NULL;
	}
		
	if (m_pContext) {
		CertFreeCertificateContext(m_pContext);
		m_pContext = NULL;
	}
	if (m_hStore) {
		CertCloseStore(m_hStore, CERT_CLOSE_STORE_FORCE_FLAG);
		m_hStore = NULL;
	}
	if (m_hSendThread) {
		CloseHandle(m_hSendThread);
		m_hSendThread = NULL;
	}
	if (m_hReceiveThread) {
		CloseHandle(m_hReceiveThread);
		m_hReceiveThread = NULL;
	}
	if (m_hReceiveStreamThread) {
		CloseHandle(m_hReceiveStreamThread);
		m_hReceiveStreamThread = NULL;
	}
	if (m_p_stream) {
		delete [] m_p_stream;
		m_p_stream = NULL;
	}
}

bool CSslConnection::SetClientCert()
{
	char *lpszStoreName;
	switch (m_certStoreType) {
	case certStoreMY:
		lpszStoreName = "MY";
		break;
	case certStoreCA:
		lpszStoreName = "CA";
		break;
	case certStoreROOT:
		lpszStoreName = "ROOT";
		break;
	case certStoreSPC:
		lpszStoreName = "SPC";
		break;
	}

	std::string tmpstr(lpszStoreName);
 	m_hStore = CertOpenSystemStore(NULL, str_to_wstr(tmpstr).c_str());
	if (!m_hStore) {
		m_strLastError = "Cannot open system store ";
		m_strLastError += lpszStoreName;
		m_lastErrorCode = GetLastError();
		ClearHandles();
		return false;
	}
	
	m_pContext = FindCertWithOUNITName();

	if (!m_pContext) {
		m_strLastError = "Cannot find the required certificate";
		m_lastErrorCode = GetLastError();
		ClearHandles();
		return false;
	}
	
	// INTERNET_OPTION_CLIENT_CERT_CONTEXT is 84
	int res = InternetSetOption(m_hRequest, 
							INTERNET_OPTION_CLIENT_CERT_CONTEXT, 
							(void *) m_pContext, sizeof(CERT_CONTEXT));
	if (!res) {
		m_strLastError = "Cannot set certificate context";
		m_lastErrorCode = GetLastError();
		ClearHandles();
		return false;
	}
	
	return true;
}

PCCERT_CONTEXT CSslConnection::FindCertWithOUNITName()
{
	//This function performs a certificate contex search
	//by the organizational unit name of the issuer
	//Take this function as a sample for your possible different search functions
	PCCERT_CONTEXT pCertContext = NULL;		
	CERT_RDN certRDN;

	certRDN.cRDNAttr = 1;
	certRDN.rgRDNAttr = new CERT_RDN_ATTR;
	certRDN.rgRDNAttr->pszObjId = szOID_ORGANIZATIONAL_UNIT_NAME;
	certRDN.rgRDNAttr->dwValueType = CERT_RDN_ANY_TYPE;
	certRDN.rgRDNAttr->Value.pbData = (BYTE *) m_strOName.c_str();
	certRDN.rgRDNAttr->Value.cbData = strlen(m_strOName.c_str());

	pCertContext = CertFindCertificateInStore(m_hStore, 
		X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
		0, CERT_FIND_ISSUER_ATTR, &certRDN, NULL);
	

	delete certRDN.rgRDNAttr;
	return pCertContext;
}

DWORD WINAPI ThreadProcGetRequestResult(LPVOID lpParam)
{
	CSslConnection *pConn = (CSslConnection *)lpParam;
	if (pConn == NULL) {
		return -1;
	}
	string strResult;
	DWORD dwNumberOfBytesRead;
	char sz[1024];
	int result; 
	do {
		result = InternetReadFile(pConn->GetHRequest(), sz, 1023, &dwNumberOfBytesRead);												
		sz[dwNumberOfBytesRead] = '\0';
		int x = strlen(sz);
		strResult += sz;
		memset(sz, 0, 1024);
			
	} while(result && dwNumberOfBytesRead != 0);	
	pConn->SetResponseStr(strResult);
	return 0;
}

string CSslConnection::GetRequestResult()
{
	m_strResponse.clear();
	m_hReceiveThread = CreateThread(NULL, 0, ThreadProcGetRequestResult, (void*)this, 0, &m_dwReceiveThreadID);
	if (WaitForSingleObject(m_hReceiveThread, m_dwTimeout) == WAIT_TIMEOUT) {
		m_strLastError = "perform http get request result timeout";
		m_lastErrorCode = GetLastError();
        return "RESP_TIMEOUT"; 
	}
	DWORD dwExitCode = 0;
	if (!GetExitCodeThread(m_hReceiveThread, &dwExitCode)) {
		m_strLastError = "Error on GetExitCodeThread";
		m_lastErrorCode = GetLastError();
        return "RESP_TIMEOUT";
    }
	if (m_lastErrorCode != 0) {
		return "ErrCode:[" + ConvertToString(m_lastErrorCode) + "]";
	}
	return m_strResponse;

	/*DWORD dwNumberOfBytesRead;
	char sz[1024];
	string strResult;
	int result; 
	do {
		result = InternetReadFile(m_hRequest, sz, 1023, &dwNumberOfBytesRead);												
		sz[dwNumberOfBytesRead] = '\0';
		int x = strlen(sz);
		strResult += sz;
		memset(sz, 0, 1024);
			
	} while(result && dwNumberOfBytesRead != 0);	
	return strResult;*/
}


DWORD WINAPI ThreadProcGetRequestStream(LPVOID lpParam)
{
	CSslConnection *pConn = (CSslConnection *)lpParam;
	if (pConn == NULL || pConn->GetStream() == NULL) {
		return -1;
	}
	DWORD dwNumberOfBytesRead;
	char sz[1024];
	int result; 
	int len = 0;
	do {
		memset(sz, 0, sizeof(sz));
		result = InternetReadFile(pConn->GetHRequest(), sz, 1023, &dwNumberOfBytesRead);												
		if ((len + dwNumberOfBytesRead) > pConn->GetStreamLength())
			return -1;
		memcpy(pConn->GetStream()+len, sz, dwNumberOfBytesRead);
		len += dwNumberOfBytesRead;
			
	} while(result && dwNumberOfBytesRead != 0);	
	pConn->SetStreamLength(len);
	return 0;
}

int CSslConnection::GetRequestStream()
{
	if (m_p_stream) {
		delete [] m_p_stream;
		m_p_stream = NULL;
	}
	m_p_stream = new char[50000];
	m_len_stream = 50000;
	m_hReceiveStreamThread = CreateThread(NULL, 0, ThreadProcGetRequestStream, (void*)this, 0, &m_dwReceiveStreamThreadID);
	if (WaitForSingleObject(m_hReceiveStreamThread, m_dwTimeout) == WAIT_TIMEOUT) {
		m_strLastError = "perform http get request result timeout";
		m_lastErrorCode = GetLastError();
        return -1; 
	}
	DWORD dwExitCode = 0;
	if (!GetExitCodeThread(m_hReceiveStreamThread, &dwExitCode)) {
		m_strLastError = "Error on GetExitCodeThread";
		m_lastErrorCode = GetLastError();
        return -1;
    }
	if (m_lastErrorCode != 0) {
		return -1;
	}
	return 0;

}